/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package com.factory.server.analyser;

import com.factory.server.algorithms.BellmanFord;
import com.factory.server.algorithms.EdmondsAlgorithm;
import com.factory.server.algorithms.Eratosthenes;
import com.factory.server.algorithms.FloydWarshall;
import com.factory.server.algorithms.Graph;
import com.factory.server.algorithms.Kruskal;
import com.factory.server.algorithms.GraphProblemresult;
import com.factory.server.algorithms.Levenshtein;
import com.factory.server.algorithms.TopologicalOrder;
import com.factory.server.bo.Edge;
import com.factory.server.bo.Fibonacci;
import com.factory.server.bo.Prime;
import com.factory.server.bo.UserTask;
import com.factory.server.cache.OutputCache;
import com.factory.server.dao.DAODispatcherIface;
import com.factory.server.dto.EdgeDTO;
import com.factory.server.helper.BigIntegerHelper;
import com.factory.server.tsp.TSP;
import java.math.BigInteger;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.logging.Level;
import java.util.logging.Logger;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.beans.factory.annotation.Qualifier;
import org.springframework.stereotype.Service;

/**
 *
 * Analyses if result of given problem is needed to compute. If not returns
 * result from cache (DB)
 *
 * @author milos
 */
@Service
public class Analyser {

    static final Logger LOG = Logger.getLogger(Analyser.class.getName());
    @Autowired
    @Qualifier("daoDispatcher")
    private DAODispatcherIface genericDAODispacher;

    public Fibonacci analyseFibb(String index) {
        Fibonacci fibb;
        int indexValue = Integer.valueOf(index);
        List<Fibonacci> result = genericDAODispacher.getByProperty("index", BigInteger.valueOf(indexValue), Fibonacci.class);
        if ((result == null) || (result.isEmpty())) {  //no result in DB, computation needed
            com.factory.server.algorithms.Fibonacci fibbComputer = new com.factory.server.algorithms.Fibonacci();
            long computationStartTime = System.currentTimeMillis();
            BigInteger resultValue = fibbComputer.execute(indexValue);
            LOG.log(Level.INFO, "Pure Fibbonacci computation time: " + (System.currentTimeMillis() - computationStartTime));
            fibb = new Fibonacci();
            fibb.setIndex(BigInteger.valueOf(indexValue));
            LOG.log(Level.INFO, "Storing Fibbonacci result started. Time: " + System.currentTimeMillis());

            fibb.setValue(BigIntegerHelper.toString(resultValue));
            return fibb;
        } else {
            return result.get(0);
        }
    }

    public GraphProblemresult analyseSpannTree(List<EdgeDTO> edgeDTOList, int nodesCount, UserTask userTask) {
        boolean isOriented = Boolean.parseBoolean(userTask.getNote()); //note contains information about orientation. Is set while scheduling
        if (!isOriented) { //algorithm is chosen by orientation
            Kruskal kruskal = new Kruskal();
            long computationStartTime = System.currentTimeMillis();
            GraphProblemresult result = kruskal.execute(edgeDTOList, nodesCount);
            LOG.log(Level.INFO, "Pure Kruskal computation time: " + (System.currentTimeMillis() - computationStartTime));
            return result;
        } else {
            //Edmonds
            long computationStartTime = System.currentTimeMillis();

            EdmondsAlgorithm edmondsAlgorithm = new EdmondsAlgorithm(nodesCount);
            GraphProblemresult result = edmondsAlgorithm.run(edgeDTOList, nodesCount);
            LOG.log(Level.INFO, "Pure Edmonds computation time: " + (System.currentTimeMillis() - computationStartTime));
            return result;
        }
    }

    public String analyseTSP(List<EdgeDTO> edgeDTOList) {

        //get nodes count
        HashSet<Integer> nodesSet = new HashSet<Integer>();
        for (EdgeDTO edge1 : edgeDTOList) {
            nodesSet.add(edge1.getSource());
            nodesSet.add(edge1.getTarget());
        }
        TSP tsp = new TSP();
        tsp.initialize(nodesSet.size(), edgeDTOList);
        return tsp.run(tsp);
    }

    public GraphProblemresult analyseSPT(List<Edge> edgeList, UserTask userTask) {
        Set<Integer> nodes = new HashSet<Integer>();
        boolean betweenAllNodes = true;
        Integer source = 0;
        Integer target = 0;
        List<EdgeDTO> edgeDTOList = new ArrayList<EdgeDTO>(edgeList.size());

        for (Edge edge : edgeList) {
            if (edge.getSource() == null) {
                betweenAllNodes = false;
                target = edge.getTarget();
                continue;
            }
            if (edge.getTarget() == null) {
                source = edge.getSource();
                continue;
            }

            nodes.add(edge.getSource());
            nodes.add(edge.getTarget());
            edgeDTOList.add(new EdgeDTO(edge.getSource(), edge.getTarget(), edge.getCost()));
        }

        if (userTask.getNote() != null && (userTask.getNote().equals("FLOYD-WARSHALL") || userTask.getNote().equals("BELLMAN-FORD"))) {
            if (userTask.getNote().equals("FLOYD-WARSHALL")) {
                FloydWarshall floydWarshall = new FloydWarshall();
                long computationStartTime = System.currentTimeMillis();
                GraphProblemresult result = floydWarshall.execute(edgeDTOList, nodes.size());
                LOG.log(Level.INFO, "Pure Floyd-Warshall computation time: " + (System.currentTimeMillis() - computationStartTime));
                return result;
            } else {
                //remove first and second edges (source and target nodes)
                long computationStartTime = System.currentTimeMillis();
                BellmanFord bellmanFord = new BellmanFord();
                GraphProblemresult result = bellmanFord.execute(edgeDTOList, nodes.size(), source, target);
                LOG.log(Level.INFO, "Pure Bellman-Ford computation time: " + (System.currentTimeMillis() - computationStartTime));
                return result;
            }

        } else {
            if (betweenAllNodes) {
                LOG.log(Level.INFO, "SPT analyze finished: Floyd-Warshall algorithm will be used.");
                FloydWarshall floydWarshall = new FloydWarshall();
                long computationStartTime = System.currentTimeMillis();
                GraphProblemresult result = floydWarshall.execute(edgeDTOList, nodes.size());
                LOG.log(Level.INFO, "Pure Floyd-Warshall computation time: " + (System.currentTimeMillis() - computationStartTime));
                return result;
            } else {
                edgeDTOList.remove(0);
                edgeDTOList.remove(0);
                LOG.log(Level.INFO, "SPT analyze finished: Bellman-Ford algorithm will be used.");
                long computationStartTime = System.currentTimeMillis();
                BellmanFord bellmanFord = new BellmanFord();
                GraphProblemresult result = bellmanFord.execute(edgeDTOList, nodes.size(), 1, 1);
                LOG.log(Level.INFO, "Pure Bellman-Ford computation time: " + (System.currentTimeMillis() - computationStartTime));
                return result;
            }
        }
    }

    public GraphProblemresult analyseTopologicalOrder(Graph graph) {
        LOG.log(Level.INFO, "Topological order compute...");
        TopologicalOrder topologicalOrder = new TopologicalOrder();
        return topologicalOrder.execute(graph);
    }

    public List<Prime> analyseEratosthenes(String value, int maximalPrime) {
        LOG.log(Level.INFO, "Eratosthenes compute... ");

        int inputValue = Integer.parseInt(value);

        if (inputValue <= maximalPrime) { //is the biggest already computed prime bigger than input value?
            LOG.log(Level.INFO, "Result is in cache. No computation needed.");
            return null;
        } else {
            LOG.log(Level.INFO, "Result is not in cache. Going to compute...");
            Eratosthenes eratosthenes = new Eratosthenes();

            //computation time is used only for better performance scheduling
            long computationStartTime = System.currentTimeMillis();

            OutputCache.primes.addAll(eratosthenes.execute(1, Integer.valueOf(value), maximalPrime));
            LOG.log(Level.INFO, "Pure Erathostenes computation time: " + (System.currentTimeMillis() - computationStartTime));
            return OutputCache.primes;
        }
    }

    public List<Integer> analyseLevenshtein(String text, String pattern, int maxDistance) {
        LOG.log(Level.INFO, "Levenshtein distance compute...");
        long computationStartTime = System.currentTimeMillis();
        Levenshtein levenshtein = new Levenshtein();
        List<Integer> levenshteinDistance = levenshtein.levenshteinDistance(text, pattern, maxDistance);
        LOG.log(Level.INFO, "Pure Levenstein computation time: " + (System.currentTimeMillis() - computationStartTime));
        return levenshteinDistance;
    }
}
